/*
 * Copyright 2008 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package net.thick4.context;

import java.beans.Introspector;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Iterator;
import java.util.LinkedList;

import net.thick4.deploy.DeploymentMode;

/**
 * HotDeploy対象のクラスを再読み込みするため、{@link Reloadable} インターフェース実装クラスの
 * 管理をおこなうユーティリティ・クラスです。
 */
public class ReloadableUtil {
	
	/**
	 * Sessionデータの再構築をおこなうクラス名です。
	 */
	public static final String REBUILDER_CLASS_NAME = "net.thick4.context.ReloadableUtil$RebuilderImpl";
	
	/**
	 * 管理対象の{@link Reloadable}実装クラスのリストです
	 */
    protected static final LinkedList reloadables = new LinkedList();
    
    /**
     * 指定したインスタンスを管理対象に追加します。 
     * @param reloadable 管理対象に追加するインスタンス
     */
    public static void add(final Reloadable reloadable) {
    	if (DeploymentMode.isDevelopmentMode()) {
    		reloadables.add(reloadable);
    	}
    }

    /**
     * 指定したインスタンスを管理対象からはずします。
     * @param disposable 管理対象からはずす対象インスタンス
     */
    public static void remove(final Reloadable reloadable) {
        reloadables.remove(reloadable);
    }

    /**
     * 管理対象のインスタンスを再読み込みします。
     */
    public static void reload() {
    	if (DeploymentMode.isDevelopmentMode()) {
	        Introspector.flushCaches();

	        Iterator it = reloadables.iterator();
	        while (it.hasNext()) {
	            final Reloadable reloadable = (Reloadable) it.next();
	            try {
	            	reloadable.reload();
	            } catch (final Throwable t) {
	                t.printStackTrace(); // must not use Logger.
	            }
	        }
    	}
    }
    	
    /**
	 * Serialize機構を用いて、ClassLoaderの異なるオブジェクトの復元をおこないます。
	 * @param value 復元対象オブジェクト
	 * @return 現在のClassLoaderに対応したオブジェクト
	 */
	public static Object rebuild(Object value) {
		if (DeploymentMode.isProductionMode()) {
			return value;
		}

		if (value == null) {
			return null;
		}
		
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        Class rebuilderClass;
		try {
			rebuilderClass = loader.loadClass(REBUILDER_CLASS_NAME);
		} catch (ClassNotFoundException e) {
			return value; 
		}

        Rebuilder rebuilder;
		try {
			rebuilder = (Rebuilder) rebuilderClass.newInstance();
		} catch (IllegalAccessException e) {
			return value; 
		} catch (InstantiationException e) {
			return value; 
		}
        
        return rebuilder.rebuild(value);
	}
	
	/**
	 * Sessionデータの再構築をおこなうクラスの実装するインターフェースです。
	 */
    public interface Rebuilder {
    	
    	/**
    	 * Sessionに含まれるオブジェクトの再構築をおこないます。
    	 * @param value Sessionに含まれるオブジェクト
    	 */
        Object rebuild(Object value);
    }
	
	/**
	 * Sessionデータの再構築をおこなうクラスです。
	 */
    public static class RebuilderImpl implements Rebuilder {
    	
    	/**
    	 * Serialize機構を用いてSessionデータの再構築をおこないます。 
    	 * @param value Sessionに含まれるオブジェクト
    	 * @return 現在のClassLoader用に再構築されたオブジェクト
    	 */
        public Object rebuild(Object value) {
            try {
                final ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
                final ObjectOutputStream oos = new ObjectOutputStream(baos);
                oos.writeObject(value);
                oos.close();

                final ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
                final ObjectInputStream ois = new ObjectInputStream(bais);
                return ois.readObject();
            } catch (final Throwable t) {
                return value;
            }
        }

    }

}

